package org.hxzon.nioclient.impl;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Set;

import org.hxzon.nioclient.NioClient;
import org.hxzon.nioclient.RequestQueue;
import org.hxzon.nioclient.ResponseQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class NioClientImpl implements NioClient, Runnable {
    private static final Logger logger = LoggerFactory.getLogger(NioClientImpl.class);
    //
    private final SocketChannel sc;
    private final Selector selector;
    private final RequestQueue requestQueue;
    private final ResponseQueue responseQueue;
    private boolean running;

    //------------
    public NioClientImpl(SocketChannel channel,//
            RequestQueue reqQueue, ResponseQueue resQueue) throws IOException {
        this.sc = channel;
        sc.configureBlocking(false);
        requestQueue = reqQueue;
        responseQueue = resQueue;
        selector = Selector.open();
    }

    public NioClientImpl(SocketAddress socketAddress,//
            RequestQueue reqQueue, ResponseQueue resQueue) throws IOException {
        this(SocketChannel.open(socketAddress), reqQueue, resQueue);
    }

    public NioClientImpl(int serverPort,//
            RequestQueue reqQueue, ResponseQueue resQueue) throws IOException {
        this(new InetSocketAddress(serverPort), reqQueue, resQueue);
    }

    //------------
    public void request(ByteBuffer reqBuf) {
        this.requestQueue.enqueue(reqBuf);
        selector.wakeup();
    }

    public ByteBuffer nextResponse() {
        selector.wakeup();
        return this.responseQueue.nextResponse();
    }

    //------------
    public void start() {
        running = true;
        Thread thread = new Thread(this);
        thread.start();
    }

    public void stop() {
        running = false;
    }

    //-------------------------
    public void run() {
        logger.trace("NioClient run thread");
        try {
            while (!sc.finishConnect()) {

            }
            sc.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_WRITE);
            while (running) {
                selector.select();
                Set<SelectionKey> keys = selector.selectedKeys();

                for (SelectionKey key : keys) {
                    handle(key);
                }

                keys.clear();
            }
        } catch (Exception e) {

        }
    }

    public void handle(SelectionKey key) throws IOException {
        if (key.isConnectable()) {
            key.channel().register(key.selector(), SelectionKey.OP_WRITE);
        }
        if (key.isWritable()) {
            this.requestQueue.drainTo((ByteChannel) key.channel());
//            if (this.requestQueue.isEmpty()) {
//                key.channel().register(key.selector(), SelectionKey.OP_READ);
//            } else {
//                key.channel().register(key.selector(), SelectionKey.OP_WRITE);
//            }
            key.channel().register(key.selector(), SelectionKey.OP_WRITE | SelectionKey.OP_READ);
        }
        if (key.isReadable()) {
            this.responseQueue.fillFrom((ByteChannel) key.channel());
            key.channel().register(key.selector(), SelectionKey.OP_WRITE);
        }
    }
}
